﻿using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Data;
using System.Windows.Input;
using GeometryViz3D.Model;
using GeometryViz3D.Services;
using CoreMVVM.ViewModels;
using CoreMVVM.Commands;
using CoreMVVM.Services;
using GeometryViz3D.Views;

namespace GeometryViz3D.ViewModels
{
    public class MainWindowViewModel : ViewModelBase, IMainViewModel
    {
        #region Fields

        private G3DModel m_model;
        private string m_modelFileName;

        private ElementCollection<PointViewModel> m_allPoints
            = new ElementCollection<PointViewModel>();

        private ElementCollection<LineViewModel> m_allLines
            = new ElementCollection<LineViewModel>();

        private ElementCollection<PlaneViewModel> m_allPlanes
            = new ElementCollection<PlaneViewModel>();

        ObservableCollection<string> m_colors
            = new ObservableCollection<string>(G3DColors.Colors);

        ModelEditorViewModel m_modelEditorViewModel;

        #region Commands

        /// <summary>
        /// The Command to create a new model.
        /// </summary>
        private DelegateCommand m_NewModelCommand;

        /// <summary>
        /// The Command to open a model.
        /// </summary>
        private DelegateCommand m_OpenModelCommand;

        /// <summary>
        /// The Command to save a model.
        /// </summary>
        private DelegateCommand m_SaveModelCommand;

        /// <summary>
        /// The Command to save a model as another file.
        /// </summary>
        private DelegateCommand m_SaveAsModelCommand;

        private DelegateCommand m_AddLineCommand;
        private DelegateCommand m_AddPointCommand;
        private DelegateCommand m_EditModelCommand;

        /// <summary>
        /// The Command to exit the application.
        /// </summary>
        private DelegateCommand m_ExitCommand;

        #endregion

        #region Services

        private IOpenFileService m_openFileService;
        private ISaveFileService m_saveFileService;
        private IMessageBoxService m_messageBoxService;
        //private IAddPointService m_addPointService;
        //private IAddLineService m_addLineService;
        //private IEditModelService m_editModelService;

        private IUIVisualizerService m_uiVisualService = null;

        #endregion

        #endregion

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="MainWindowViewModel"/> class.
        /// </summary>
        public MainWindowViewModel(IServicesFactory servicesFactory)
        {
            m_model = new G3DModel();

            m_modelEditorViewModel = new ModelEditorViewModel(this);

            #region Services Registration

            //By default, registering the service over again doesnt overwrite the Service - you need to pass the bool parameter to the RegisterService call
            ViewModelBase.ServiceProvider.RegisterService<IOpenFileService>(servicesFactory.GetOpenFileService());
            m_openFileService = GetService<IOpenFileService>();

            ViewModelBase.ServiceProvider.RegisterService<ISaveFileService>(servicesFactory.GetSaveFileService());
            m_saveFileService = GetService<ISaveFileService>();

            ViewModelBase.ServiceProvider.RegisterService<IMessageBoxService>(servicesFactory.GetMessageBoxService());
            m_messageBoxService = GetService<IMessageBoxService>();

#if false
            ViewModelBase.ServiceProvider.RegisterService<IAddPointService>(servicesFactory.GetAddPointService());
            m_addPointService = GetService<IAddPointService>();

            ViewModelBase.ServiceProvider.RegisterService<IAddLineService>(servicesFactory.GetAddLineService());
            m_addLineService = GetService<IAddLineService>();

            ViewModelBase.ServiceProvider.RegisterService<IEditModelService>(servicesFactory.GetEditModelService());
            m_editModelService = GetService<IEditModelService>();
#endif
            m_uiVisualService = GetService<IUIVisualizerService>();

            #endregion
        }

        #endregion

        #region Properties

        public G3DModel Model
        {
            get { return m_model; }
            set 
            { 
                m_model = value;

                RefreshModel();
                
                OnPropertyChanged("Model");
            }
        }

        public ModelEditorViewModel ModelEditorViewModel
        {
            get { return m_modelEditorViewModel; }
        }

        public void RaiseModelChangedEvent()
        {
            OnPropertyChanged("Model");
        }

        public ElementCollection<PointViewModel> AllPoints
        {
            get 
            { 
                return m_allPoints; 
            }
        }

        public ElementCollection<LineViewModel> AllLines
        {
            get 
            { 
                return m_allLines; 
            }
        }

        public ElementCollection<PlaneViewModel> AllPlanes
        {
            get 
            { 
                return m_allPlanes; 
            }
        }

        public string ModelFileName
        {
            get { return m_modelFileName; }
            set
            {
                m_modelFileName = value;
                OnPropertyChanged("ModelFileName");
            }
        }

        public ObservableCollection<string> Colors
        {
            get
            {
                return m_colors;
            }
        }

        #region Commands

        /// <summary>
        /// Gets the new model command.
        /// </summary>
        /// <value>The new model command.</value>
        public ICommand NewModelCommand
        {
            get
            {
                if (m_NewModelCommand == null)
                {
                    m_NewModelCommand = new DelegateCommand(NewModel);
                }

                return m_NewModelCommand;
            }
        }

        /// <summary>
        /// Gets the open model command.
        /// </summary>
        /// <value>The open model command.</value>
        public ICommand OpenModelCommand
        {
            get
            {
                if (m_OpenModelCommand == null)
                {
                    m_OpenModelCommand = new DelegateCommand(OpenModel);
                }

                return m_OpenModelCommand;
            }
        }

        /// <summary>
        /// Gets the save model command.
        /// </summary>
        /// <value>The save model command.</value>
        public ICommand SaveModelCommand
        {
            get
            {
                if (m_SaveModelCommand == null)
                {
                    m_SaveModelCommand = new DelegateCommand(SaveModel, CanSaveModel);
                }

                return m_SaveModelCommand;
            }
        }

        /// <summary>
        /// Gets the save model command.
        /// </summary>
        /// <value>The save model command.</value>
        public ICommand SaveAsModelCommand
        {
            get
            {
                if (m_SaveAsModelCommand == null)
                {
                    m_SaveAsModelCommand = new DelegateCommand(SaveAsModel, CanSaveAsModel);
                }

                return m_SaveAsModelCommand;
            }
        }

        public ICommand AddPointCommand
        {
            get
            {
                if (m_AddPointCommand == null)
                {
                    m_AddPointCommand = new DelegateCommand(AddPoint);
                }

                return m_AddPointCommand;
            }
        }

        public ICommand AddLineCommand
        {
            get
            {
                if (m_AddLineCommand == null)
                {
                    m_AddLineCommand = new DelegateCommand(AddLine);
                }

                return m_AddLineCommand;
            }
        }

        public ICommand EditModelCommand
        {
            get
            {
                if (m_EditModelCommand == null)
                {
                    m_EditModelCommand = new DelegateCommand(EditModel);
                }

                return m_EditModelCommand;
            }
        }

        /// <summary>
        /// Gets the exit command.
        /// </summary>
        /// <value>The exit command.</value>
        public ICommand ExitCommand
        {
            get
            {
                if(m_ExitCommand == null)
                {
                    m_ExitCommand = new DelegateCommand(Exit);
                }

                return m_ExitCommand;
            }
        }

        #endregion

        #endregion

        #region Event Handlers

        /// <summary>
        /// Create a new model.
        /// </summary>
        private void NewModel()
        {
            if (m_model.IsEmpty || AskSaveModel() == MessageBoxResult.No)
            {
#if false
                EditModelViewModel vm = new EditModelViewModel();
                bool? result = m_uiVisualService.ShowDialog("EditModelDialog", vm);

                if (result.HasValue && result.Value)
                {
                    this.Model = vm.Model;
                }
#endif
                this.Model = new G3DModel();
                this.Model.FileName = "Untitled";
            }
        }

        /// <summary>
        /// Open a model.
        /// </summary>
        private void OpenModel()
        {
            if (m_model.IsDirty && AskSaveModel() == MessageBoxResult.Cancel)
            {
                return;
            }

            string fileName = GetOpenFileName();
            if (!string.IsNullOrEmpty(fileName))
            {
                LoadModel(fileName);
            }
        }

        private MessageBoxResult AskSaveModel()
        {
            /* The model is dirty. Ask if the user wants to save it.
             * If the user clicked on the Cancel button, just return.
             */
            MessageBoxResult result = m_messageBoxService.Show("Do you want to save the model?",
                "Geometry 3D", MessageBoxButton.YesNoCancel);

            if (result == MessageBoxResult.Yes)
            {
                if (m_model.IsUntitled)
                {
                    string fileName = GetSaveFileName();
                    if (!string.IsNullOrEmpty(fileName))
                    {
                        m_model.SaveAs(fileName);
                        this.ModelFileName = fileName;
                    }
                }
                else
                {
                    m_model.Save();
                }
            }

            return result;
        }

        public void LoadModel(string fileName)
        {
            this.Model = G3DModel.Load(fileName);
            this.ModelFileName = fileName;
        }

        private string GetSaveFileName()
        {
            m_saveFileService.Filter = "G3D Files|*.g3d";
            bool? ok = m_saveFileService.ShowDialog(null);

            if (ok.HasValue && ok.Value)
            {
                return m_saveFileService.SavedFileName;
            }
            else
            {
                return null;
            }
        }

        private string GetOpenFileName()
        {
            m_openFileService.Filter = "G3D Files|*.g3d";
            bool? result = m_openFileService.ShowDialog(null);
            if (result.HasValue && result.Value)
            {
                return m_openFileService.OpenedFileName;
            }
            else
            {
                return null;
            }
        }

        /// <summary>
        /// Determines whether we can save the model.
        /// </summary>
        /// <returns>
        /// 	<c>true</c> if the model is not empty; otherwise, <c>false</c>.
        /// </returns>
        private bool CanSaveModel()
        {
            return !m_model.IsEmpty;
        }

        /// <summary>
        /// Save the model.
        /// </summary>
        private void SaveModel()
        {
            if (m_model.IsUntitled)
                SaveAsModel();
            else
                m_model.Save();
        }

        /// <summary>
        /// Determines whether we can save the model.
        /// </summary>
        /// <returns>
        /// 	<c>true</c> if the model is not empty; otherwise, <c>false</c>.
        /// </returns>
        private bool CanSaveAsModel()
        {
            return !m_model.IsEmpty;
        }

        /// <summary>
        /// Save the model.
        /// </summary>
        private void SaveAsModel()
        {
            string fileName = GetSaveFileName();

            if (!string.IsNullOrEmpty(fileName))
            {
                m_model.SaveAs(fileName);

                ModelFileName = fileName;
                //OnPropertyChanged("ModelFileName");
            }
        }

        private void AddPoint()
        {
#if false
            m_addPointService.PropertyChanged += ModelChanged;
            m_addPointService.AddPoints(Model);
            m_addPointService.PropertyChanged -= ModelChanged;
#endif
            AddPointViewModel vm = new AddPointViewModel(Model);
            vm.PropertyChanged += SubViewModelPropertyChanged;
            m_uiVisualService.ShowDialog("AddPointDialog", vm);
        }

        private void AddLine()
        {
#if false
            m_addLineService.PropertyChanged += ModelChanged;
            m_addLineService.AddLines(Model);
            m_addLineService.PropertyChanged -= ModelChanged;
#endif
            AddLineViewModel vm = new AddLineViewModel(Model);
            vm.PropertyChanged += SubViewModelPropertyChanged;
            m_uiVisualService.ShowDialog("AddLineDialog", vm);
        }

        private void EditModel()
        {
#if false
            EditModelViewModel vm = new EditModelViewModel(Model);
            bool? result = m_uiVisualService.ShowDialog("EditModelDialog", vm);

            if (result.HasValue && result.Value)
            {
                G3DModel newModel = vm.Model;
                newModel.FileName = m_model.FileName;
                this.Model = newModel;
            }
#endif
        }

        private void SubViewModelPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "Model")
            {
                OnPropertyChanged("Model");
            }
        }

        /// <summary>
        /// Determines whether application can be exited.
        /// </summary>
        /// <returns>
        /// 	<c>true</c> if application can be exited; otherwise, <c>false</c>.
        /// </returns>
        private bool CanExit()
        {
            return true;
        }

        /// <summary>
        /// Exits the application.
        /// </summary>
        private void Exit()
        {
            if (m_model.IsDirty)
            {
                if (AskSaveModel() == MessageBoxResult.Cancel)
                {
                    return;
                }
            }

            Application.Current.Shutdown();
        }

        #endregion

        #region Private Methods

        private void RefreshModel()
        {
            RefreshPoints();
            RefreshLines();
            RefreshPlanes();
        }

        private void RefreshPoints()
        {
            m_allPoints.Clear();

            foreach (var p in m_model.Points)
            {
                PointViewModel pvm = new PointViewModel(p, this);
                m_allPoints.Add(pvm);
            }
        }

        private void RefreshLines()
        {
            m_allLines.Clear();

            foreach (var l in m_model.Lines)
            {
                LineViewModel lvm = new LineViewModel(l, this);
                m_allLines.Add(lvm);
            }
        }

        private void RefreshPlanes()
        {
            m_allPlanes.Clear();

            foreach (var p in m_model.Planes)
            {
                PlaneViewModel pvm = new PlaneViewModel(p, this);
                m_allPlanes.Add(pvm);
            }
        }


        #endregion
    }
}
